﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Invini.Tools
{
    public struct Base36
    {
        private static readonly char[] chars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                                                 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
                                                 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
                                                 'U', 'V', 'W', 'X', 'Y', 'Z' };
        private long number;
        public Base36(Base36 base36)
        {
            this.number = base36.number;
        }
        public Base36(long number)
        {
            this.number = number;
        }
        public Base36(string number)
        {
            this.number = 0;
            IEnumerable<char> reversed = number.ToUpper().Reverse();
            long power = 1;
            foreach (char c in reversed)
            {
                int position = Array.IndexOf<char>(chars, c);
                if (position < 0)
                    throw new InvalidCastException(String.Concat("Base36 string '", number, "' contains invalid character '", c, "'."));

                this.number += position * power;
                power *= chars.Length;
            }
        }
        
        public override string ToString()
        {
            if (number < 0) throw new ArgumentOutOfRangeException("input", number, "input cannot be negative");

            var result = new Stack<char>();
            while (number != 0)
            {
                result.Push(chars[this.number % 36]);
                number /= 36;
            }
            return new string(result.ToArray());
        }
        public override bool Equals(object obj)
        {
            if (obj == null || !(obj is Base36))
                return false;

            Base36 base36 = (Base36)obj;

            return this.number == base36.number;
        }

        public bool Equals(Base36 base36)
        {
            return this.number == base36.number;
        }

        public override int GetHashCode()
        {
            return this.number.GetHashCode();
        }

        public static explicit operator long(Base36 base36)
        {
            return base36.number;
        }
        public static explicit operator Base36(long number)
        {
            return new Base36(number);
        }
        public static explicit operator string(Base36 base36)
        {
            return base36.ToString();
        }
        public static explicit operator Base36(string number)
        {
            return new Base36(number);
        }
        public static Base36 operator +(Base36 b1, Base36 b2)
        {
            return new Base36(b1.number + b2.number);
        }
        public static Base36 operator +(Base36 b1, long b2)
        {
            return new Base36(b1.number + b2);
        }
        public static long operator +(long b1, Base36 b2)
        {
            return b1 + b2.number;
        }
        public static Base36 operator ++(Base36 base36)
        {
            return new Base36(base36.number + 1);
        }
        public static Base36 operator --(Base36 base36)
        {
            return new Base36(base36.number - 1);
        }
        public static Base36 operator -(Base36 b1, Base36 b2)
        {
            return new Base36(b1.number - b2.number);
        }
        public static Base36 operator -(Base36 b1, long b2)
        {
            return new Base36(b1.number - b2);
        }
        public static long operator -(long b1, Base36 b2)
        {
            return b1 - b2.number;
        }
        public static Base36 operator *(Base36 b1, Base36 b2)
        {
            return new Base36(b1.number * b2.number);
        }
        public static Base36 operator *(Base36 b1, long b2)
        {
            return new Base36(b1.number * b2);
        }
        public static long operator *(long b1, Base36 b2)
        {
            return b1 * b2.number;
        }
        public static bool operator ==(Base36 b1, Base36 b2)
        {
            return b1.number == b2.number;
        }
        public static bool operator ==(Base36 b1, long b2)
        {
            return b1.number == b2;
        }
        public static bool operator ==(long b1, Base36 b2)
        {
            return b1 == b2.number;
        }
        public static bool operator !=(Base36 b1, Base36 b2)
        {
            return b1.number != b2.number;
        }
        public static bool operator !=(Base36 b1, long b2)
        {
            return b1.number != b2;
        }
        public static bool operator !=(long b1, Base36 b2)
        {
            return b1 != b2.number;
        }
        public static bool operator >(Base36 b1, Base36 b2)
        {
            return b1.number > b2.number;
        }
        public static bool operator >(Base36 b1, long b2)
        {
            return b1.number > b2;
        }
        public static bool operator >(long b1, Base36 b2)
        {
            return b1 > b2.number;
        }
        public static bool operator <(Base36 b1, Base36 b2)
        {
            return b1.number < b2.number;
        }
        public static bool operator <(Base36 b1, long b2)
        {
            return b1.number < b2;
        }
        public static bool operator <(long b1, Base36 b2)
        {
            return b1 < b2.number;
        }
        public static bool operator >=(Base36 b1, Base36 b2)
        {
            return b1.number >= b2.number;
        }
        public static bool operator >=(Base36 b1, long b2)
        {
            return b1.number >= b2;
        }
        public static bool operator >=(long b1, Base36 b2)
        {
            return b1 >= b2.number;
        }
        public static bool operator <=(Base36 b1, Base36 b2)
        {
            return b1.number <= b2.number;
        }
        public static bool operator <=(Base36 b1, long b2)
        {
            return b1.number <= b2;
        }
        public static bool operator <=(long b1, Base36 b2)
        {
            return b1 <= b2.number;
        }
    }
}
